<?php

	namespace org\tekuna\plugin\pdodb;

	use \PDO;
	use \Exception;
	
	use org\tekuna\core\configuration\ConfigurationException;
	use org\tekuna\core\configuration\ConfigurationElement;
	
	use org\tekuna\framework\request\Request;
	use org\tekuna\framework\action\ActionEvent;
	use org\tekuna\framework\action\AbstractAction;

	
	/**
	 * This action executes a number of installation scripts for databases. Each
	 * install script can be executed on a defined connection and special database
	 * type only. So you can support multiple databases. In XML a install script
	 * registration looks like this:
	 * 
	 * <pdo-db:install-script
	 *    file="install.sql"
	 *    plugin="pdo-db"
	 *    db-type="mysql"
	 *    connection="myConnection"/>
	 * 
	 * The file parameter defines a relative path to the installation file. If the
	 * optional plugin parameter is given, the file is loaded out of that plugin. If
	 * the optional db-type parameter is given, the used connection is checked 
	 * to be of exactly that type.
	 * 
	 * The connection parameter is optional, too. If it is not given, the default
	 * connection is used.
	 * 
	 * All likely defined scripts are run separately in different transactions. If
	 * one script crashes, this affects not the other scripts from running.
	 * 
	 * @todo: implement special file or plugin parameter for in-plugin locations
	 */
	class InstallDbAction extends AbstractAction {
		
		private $objConnMgr;
		
		public function execute(ActionEvent $objActionEvent, Request $objRequest) {

			echo "\n";
			echo "Running PDO Database Installation Scripts\n";
			echo "-----------------------------------------\n\n";
			
			// get all script config elements
			$objConfiguration = $this -> getApplicationContext() -> getConfiguration();
			$arrInstallScripts = $objConfiguration -> getRootElement() -> getAllChildElements('pdo-db', 'install-script');
			
			// iterate all found elements
			foreach ($arrInstallScripts as $objScriptElement) {
				
				try {
					
					// get the SQL script
					$sSqlScript = $this -> getSqlScript($objScriptElement);
					
					// get the connection; returns null if skipped
					$objConn = $this -> getDbConnection($objScriptElement);
					
					// run the script on the connection
					if ($objConn != null) {

						$this -> runSqlScript($objConn, $sSqlScript);
					}
				}
				catch (Exception $objException) {
					
					// print any exception; do not break other scripts
					echo "ERROR: ". $objException -> getMessage() .' ('. get_class($objException) .")\n";
				}
			}

			echo "\n";
		}
		
		
		private function getSqlScript(ConfigurationElement $objScriptElement) {
			
			// get filename
			$sFile = $objScriptElement -> getAttribute('pdo-db', 'file') -> getValue();
			echo $sFile .' ... ';
			
			// load script from plugin if plugin defined
			if ($objScriptElement -> hasAttribute('pdo-db', 'plugin')) {
				
				// load the plugin
				$sPluginKey = $objScriptElement -> getAttribute('pdo-db', 'plugin') -> getValue();
				$objPlugin = $this -> getApplicationContext() -> getPluginManager() -> getPlugin($sPluginKey);
				
				// use a plugin resource
				$sFile = $objPlugin -> getResourceFilename($sFile);
			}
			
			// file must exist
			if (! file_exists($sFile) || ! is_readable($sFile)) {
				
				throw new ConfigurationException("The install script '$sFile' does not exist or is not readable.");
			}
			
			// return the file contents
			return file_get_contents($sFile);
		}
		
		
		private function getDbConnection(ConfigurationElement $objScriptElement) {
			
			// get connection
			$objConn = null;
			if ($objScriptElement -> hasAttribute('pdo-db', 'connection')) {
				
				// use given connection if explicitly given
				$sConnectionId = $objScriptElement -> getAttribute('pdo-db', 'connection') -> getValue();
				$objConn = $this -> objConnMgr -> getConnection($sConnectionId);
			}
			else {
				
				// use default connection otherwise
				$objConn = $this -> objConnMgr -> getDefaultConnection();
			}
			
			// check connection type if given
			if ($objScriptElement -> hasAttribute('pdo-db', 'db-type')) {
				
				$sScriptDbType = $objScriptElement -> getAttribute('pdo-db', 'db-type') -> getValue();
				$sConnectionDbType = $objConn -> getAttribute(PDO::ATTR_DRIVER_NAME);
				if ($sScriptDbType != $sConnectionDbType) {
					
					echo "SKIPPING: The script DB type '$sScriptDbType' does not match the connection DB type '$sConnectionDbType'.\n";
					return null;
				}
			}
			
			return $objConn;
		}
		
		
		private function runSqlScript(PDO $objConn, $sSqlScript) {
			
			// run script in transaction
			try {
				
				$objConn -> beginTransaction();
				$objConn -> exec($sSqlScript);
				$objConn -> commit();
				echo "OK\n";
			}
			catch (Exception $objException) {
				
				// rollback transaction if open
				// PDO::inTransaction() currently only for postgres
				// TODO: check in later PHP version if available
				//if ($objConn -> inTransaction()) {
					
					$objConn -> rollBack();
				//}
				
				// re-throw exception
				throw $objException;
			}
		}
		
		
		/**
		 * Here the Connection Manager gets injected from the context
		 * 
		 * @param ConnectionManager $objConnMgr
		 */
		public function setPdoConnectionManager(ConnectionManager $objConnMgr) {
			
			$this -> objConnMgr = $objConnMgr;
		}
	}
